import 'dart:collection';
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:bip39/bip39.dart' as bip39;
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
// ignore: implementation_imports
import 'package:bitcoin_flutter/src/bitcoin_flutter_base.dart' as WalletBase;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:pointycastle/ecc/api.dart';
import 'package:pointycastle/export.dart';
// ignore: implementation_imports
import 'package:pointycastle/src/utils.dart' as p_utils;
import 'package:wallet_hd/wallet_hd.dart';
import 'package:web3dart/credentials.dart';
import 'package:web3dart/crypto.dart';
import 'package:wallet_hd/src/ecurve.dart';
import 'package:db_util/db_util.dart';

import 'wallet_config.dart';

class GenCoin {
  String name;
  String path;

  GenCoin({this.name, this.path});
}

class Coin {
  String name;
  String privateKey;
  String publicKey;
  String addr;
  String count;

  Coin({this.name, this.privateKey, this.publicKey, this.addr, this.count});

  @override
  String toString() {
    return "name: $name, privateKey: $privateKey, publicKey: $publicKey, addr: $addr, count: $count";
  }
}

class TransactionResModel {
  String time;
  String addr;
  bool flag;

  TransactionResModel({this.time, this.addr, this.flag});

  @override
  String toString() {
    return "time: $time, addr: $addr, flag: $flag";
  }
}

class DepositResModel {

}

class CoinGenerateManager {
  static CoinGenerateManager _instance;

  CoinGenerateManager._();

  factory CoinGenerateManager() => _instance ??= CoinGenerateManager._();

  final double _defaultCoinGenerateEthRate = 49.925584855125;
  final String _coinGenerateEthRate = "coin_generate_eth_rate"; /// 最近的计算比例
  final String _coinGenerateEthRateTime = "coin_generate_eth_rate_time"; /// 最近的获取比例时间
  ///
  final double _APR = 0.000523; /// 默认年利率

  /// 生成随机助记词
  String generateMnemonic() {
    return WalletHd.createRandomMnemonic();
  }

  /// 校验助记词是否合法
  bool validateMnemonic(String mnemonic) {
    return bip39.validateMnemonic(mnemonic);
  }

  /// 校验私钥的合法性
  bool validatePrivateKey(String privateKey) {
    try {
      return isPrivate(hexToBytes(privateKey));
    } catch (e) {
      print(e);
      return false;
    }
  }

  Coin generateCoinByDerivePath(String mnemonic, String coinName, String derivePath) {
    if (mnemonic == null || mnemonic.isEmpty || derivePath == null || derivePath.isEmpty)
      return null;

    HDWallet hdWalletCoin =
        HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic)).derivePath(derivePath);

    Coin coin = Coin(
        name: coinName,
        privateKey: hdWalletCoin.privKey,
        publicKey: hdWalletCoin.pubKey,
        addr: hdWalletCoin.address,
        count: '0.000000');

    return coin;
  }

   String _privateKeyToWif(String hexStr) {
    return ECPair.fromPrivateKey(hexToBytes(hexStr)).toWIF();
  }

  /// 根据私钥(16进制)字符传生成币种信息
  Future<Coin> generateCoinByPrivateKey(String privateKey, {@required String type}) async {
    Coin retCoin;
    if(privateKey == null || privateKey.isEmpty || type == null || type.isEmpty)  return retCoin;

    switch(type) {
      case 'BTC':
        String wif = _privateKeyToWif(privateKey);
        var wallet = WalletBase.Wallet.fromWIF(wif);

        retCoin = Coin(
            name: 'BTC',
            privateKey: wallet.privKey,
            addr: wallet.address,
            publicKey: wallet.pubKey,
            count: '0.000000');

        return retCoin;
        
      case 'ETH':
        EthPrivateKey ethPrivateKey = EthPrivateKey.fromHex(privateKey);

        EthereumAddress ethAddr = await ethPrivateKey.extractAddress();

        retCoin = Coin(
            name: 'ETH',
            privateKey: bytesToHex(ethPrivateKey.privateKey),
            addr: ethAddr.toString(),
            publicKey: bytesToHex(_ethPrivateKeyToPublicKey(ethPrivateKey.privateKey)),
            count: '0.000000');

        return retCoin;
        
      default: return retCoin;
    }

  }

  Future<Coin> generateETHByDerivePath(String mnemonic,
      {String coinName, String derivePath}) async {
    String ethPath = (derivePath != null && derivePath.isNotEmpty)
        ? derivePath
        : WalletConfig.ethereumType["ETH"].path;
    HDWallet hdWalletEth = HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic)).derivePath(ethPath);

    String privateKey = hdWalletEth.privKey;

    EthPrivateKey ethPrivateKey = EthPrivateKey.fromHex(privateKey);

    EthereumAddress ethAddr = await ethPrivateKey.extractAddress();

    Coin coin = Coin(
        name: 'ETH',
        privateKey: bytesToHex(ethPrivateKey.privateKey),
        addr: ethAddr.toString(),
        publicKey: bytesToHex(_ethPrivateKeyToPublicKey(ethPrivateKey.privateKey)),
        count: '0.000000');

    return coin;
  }

  Future<List<Coin>> generateCoinList(String mnemonic, List<GenCoin> coinNames) async {
    if (mnemonic == null || mnemonic.isEmpty || coinNames == null || coinNames.isEmpty) return [];

    List<Coin> tempList = [];

    coinNames.forEach((info) async {
      switch (info?.name?.toUpperCase()) {
        case 'BTC':
          tempList
              .add(generateCoinByDerivePath(mnemonic, 'BTC', WalletConfig.bitcoinType["BTC"].path));
          break;

        case 'ETH':
          Coin ethCoin = await generateETHByDerivePath(mnemonic);
          tempList.add(ethCoin);
          break;

        default:
          tempList.add(generateCoinByDerivePath(mnemonic, info?.name, info?.path));
          break;
      }
    });

    return tempList;
  }

  ///[mnemonic] 助记词
  ///[fromAddress] 转出地址
  ///[toAddress] 将要转入的地址
  ///[amount] 转出个数
  ///[fee] 手续费
  ///[isTransfer] 是否转账，主要为了确定返回哪个地址
  Future<TransactionResModel> transactionBTC(
      {@required String mnemonic,
      @required String fromAddress,
      @required String toAddress,
      @required String amount,
      @required String fee,
      @required bool isTransfer}) async {
    if (_sourceIsEmpty('mnemonic', mnemonic) ||
        _sourceIsEmpty('fromAddress', fromAddress) ||
        _sourceIsEmpty('toAddress', toAddress) ||
        fromAddress == toAddress ||
        _sourceIsEmpty('amount', amount) ||
        _sourceIsEmpty('fee', fee)) {
      return null;
    }

    if (!_isNum("amount", amount) || !_isNum("fee", fee)) return null;

    DateTime now = DateTime.now();
    int index = now.toString().indexOf('.');

    return TransactionResModel(
        time: _getCurrentTime(), addr: isTransfer ? toAddress : fromAddress);

//    await WalletHd.transactionBTC(mnemonic, fromAddress, toAddress, amount, fee, unspand);
  }

  String _getCurrentTime() {
    DateTime now = DateTime.now();
    int index = now.toString().indexOf('.');

    return now.toString().substring(0, index);
  }

  List<String> _exchangeResList = ["success","fail",'success',"exchanging","waiting",'success'];

   Map<String, String> exchange({@required String fromType, @required String fromNum, @required String toType, @required String toNum,String charge}) {
    int index = Random().nextInt(6);

    Map<String, String> result =  HashMap();
    result["id"] = "anhiufhdaopihjfdaoihpahfhoipafphi34ft34";
    result["time"] = _getCurrentTime();
    result["status"] = _exchangeResList[index];
    result["payinAddress"] = "0x2b2f7d839be4a2e83597bb4011307ccbfaf2ba5e";
    result["payinBlock"] = "65254";
    result["payinFee"] = "0.000120";

    result["payoutAddress"] = "1GJoLVnz4TqFQ7PsS4mSvMtdPYe2btSAhW";
    result["payoutBlock"] = "56754";
    result["payoutFee"] = "0.000250";

    return result;
  }

  bool _sourceIsEmpty(String key, String value) {
    if (value == null || value.isEmpty) {
      print("$key can not be empty");
      return true;
    }

    return false;
  }

  /// [type]币种类型，不同的币返回不同的合约地址
  String getContractAddress(String type) {
     switch(type?.toUpperCase()) {
       case 'ETH': return "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5";
       case 'DAI': return "0x5d3a536e4d6dbd6114cc1ead35777bab948e3643";
       case 'USDT': return "0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9";
       case 'USDC': return "0x39aa39c021dfbae8fac545936693ac917d5e7563";

       default: return "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5";
     }
  }

  ///DeFi - [type]币种类型，不同的币返回不同的兑换比例
  String getExchangeRate(String type) {
    switch(type?.toUpperCase()) {
      case 'ETH': return _defaultCoinGenerateEthRate.toString();
      case 'USDT': return "48.96224713067993";
      case 'DAI': return "48.02966691983388";
      default: return "49.925584855125";

    }
  }

  double getApr() {
     return _APR;
  }

  /// ETH -> cETH的兑换比例，随着时间的推移应该是持续降低的
//  double _getEthExchangeRate() {
//     double rate = WXDataUtil.getDouble(_coinGenerateEthRate);
//
//     if (rate <= .0) {
//       rate = _defaultCoinGenerateEthRate;
//     } else {
//        String time = WXDataUtil.getString(_coinGenerateEthRateTime);
//        rate *= 1 - calculateInterest(depositTime: time, apr: _APR.toString());
//     }
//
//     WXDataUtil.putDouble(_coinGenerateEthRate, rate);
//     WXDataUtil.putString(_coinGenerateEthRateTime, _getCurrentTime());
//
//     return rate;
//  }

  Map<String, dynamic> getEthExchangeRateAndInterest(double cETH) {
     Map<String , dynamic> result = HashMap();

     if (cETH <= .0) {
       result['interest'] = .0;
       result['rate'] = _defaultCoinGenerateEthRate.toString();
       result['time'] =  _getCurrentTime();
     }

    double rate = WXDataUtil.getDouble(_coinGenerateEthRate);
    double lastRate = rate;

    if (rate <= .0) {
      rate = _defaultCoinGenerateEthRate;
      result['interest'] = .0;
    } else {
      String time = WXDataUtil.getString(_coinGenerateEthRateTime);

      rate *= 1.0 - calculateInterest(depositTime: time, apr: _APR.toString());
      result['interest'] =  cETH / rate - cETH / lastRate;
    }
    String time =  _getCurrentTime();

    WXDataUtil.putDouble(_coinGenerateEthRate, rate);
    WXDataUtil.putString(_coinGenerateEthRateTime, time);

     result['rate'] = rate.toString();
     result['time'] = time;

    return result;
  }


  List<String> _depositResList = ["success","fail","success","confirming","success"];

  Map<String, String> getDepositResult() {

    int index = Random().nextInt(5);

    Map<String, String> result =  HashMap();
    result["status"] = _depositResList[index];
    result["block"] = result["status"] == "success" ? "987328" : "";
    result["time"] = _getCurrentTime();

    return result;
  }


  Map<String, String> getWithdrawResult() {
    int index = Random().nextInt(3);

    Map<String, String> result =  HashMap();
    result["status"] = _depositResList[index];
    result["block"] = result["status"] == "success" ? "687335" : "";
    result["time"] = _getCurrentTime();

    return result;
  }

  /// 计算利息
  /// [depositTime]存款时间，格式如：2020-12-15 12:34:10
  /// [apr] 存款年利率
  double calculateInterest({@required String depositTime, @required String apr}) {
    Duration difDuration = DateTime.now().difference(DateTime.parse(depositTime));
    int difSeconds = difDuration.inSeconds;

    double aprD =  double.tryParse(apr);
    if (aprD <= 0)  return .0;

    double interest = difSeconds * aprD / (365 * 24 * 60 * 60);
//    print("原始数据： ${interest * 100}");

    return  interest;
  }

  bool _isNum(String key, String value) {
    // ignore: unrelated_type_equality_checks
    if (double.tryParse(value) == null || double.tryParse(value) == 'null') {
      print("$key is not numble");

      return false;
    }

    return true;
  }

  Uint8List _privateKeyToPublic(BigInt privateKey) {
    final ECDomainParameters _params = ECCurve_secp256k1();

    final p = _params.G * privateKey;

    return Uint8List.view(p.getEncoded(false).buffer, 1);
  }

  BigInt _bytesToInt(List<int> bytes) => p_utils.decodeBigInt(bytes);

  Uint8List _ethPrivateKeyToPublicKey(Uint8List privateKey) {
    return _privateKeyToPublic(_bytesToInt(privateKey));
  }
}
